﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;

namespace UDP_Lib
{
    public class UDP_Packet
    {
        public byte Type;
        public Int32 Index;
        public UInt16 Size;
        public Int16 CurPacket;
        public Int16 NumPacket;
        public byte[] Data;
        public DateTime timeRec;

        public byte[] ToByte()
        {
            byte[] BufOut = new byte[
                4 +
                Data.Length + 
                2 +
                2 +
                2 +
                4 +
                1];

            int ind = 0;

            BufOut[ind++] = 251;
            BufOut[ind++] = 51;
            BufOut[ind++] = 250;
            BufOut[ind++] = 50;

            BufOut[ind++] = (byte)(Index >> 24);
            BufOut[ind++] = (byte)(Index >> 16);
            BufOut[ind++] = (byte)(Index >> 8);
            BufOut[ind++] = (byte)(Index >> 0);

            BufOut[ind++] = (byte)(CurPacket >> 8);
            BufOut[ind++] = (byte)(CurPacket >> 0);

            BufOut[ind++] = (byte)(NumPacket >> 8);
            BufOut[ind++] = (byte)(NumPacket >> 0);

            Size = (UInt16)Data.Length;

            BufOut[ind++] = (byte)(Size >> 8);
            BufOut[ind++] = (byte)(Size >> 0);

            Array.Copy(Data, 0, BufOut, 14, Data.Length);

            return BufOut;
        }

        static public UDP_Packet FromByte(byte[] BufIn, long size)
        {
            if(BufIn==null) return null;
            if(BufIn.Length<15) return null;
            UDP_Packet up = new UDP_Packet();
            bool bFlagPreambula = true;

            if (BufIn[0] != 251 ||
                BufIn[1] != 51 ||
                BufIn[2] != 250 ||
                BufIn[3] != 50)
                bFlagPreambula = false;
            int ind=4;

            up.Index |= BufIn[ind++]; up.Index <<= 8;
            up.Index |= BufIn[ind++]; up.Index <<= 8;
            up.Index |= BufIn[ind++]; up.Index <<= 8;
            up.Index |= BufIn[ind++];

            up.CurPacket |= BufIn[ind++]; up.CurPacket <<= 8;
            up.CurPacket |= BufIn[ind++];

            up.NumPacket |= BufIn[ind++]; up.NumPacket <<= 8;
            up.NumPacket |= BufIn[ind++];

            up.Size |= BufIn[ind++]; up.Size <<= 8;
            up.Size |= BufIn[ind++];
            up.Data = new byte[up.Size];
            Array.Copy(BufIn, ind, up.Data, 0, up.Size);
            up.timeRec = DateTime.Now;
            return up;
        }
    }


    [Serializable()]
    public class UPD_Message : ISerializable
    {
        public string From;
        public List<string> To;
        public byte[] Data;
        public DateTime TimeStamp;
        public int IndexMessage;
        public UPD_Message(){}
        public UPD_Message(SerializationInfo info, StreamingContext ctxt)
        {
            //Get the values from info and assign them to the appropriate properties

            From = (string)info.GetValue("From", typeof(string));
            To = (List<string>)info.GetValue("To", typeof(List<string>));

            Data = (byte[])info.GetValue("Data", typeof(byte[]));
            TimeStamp = (DateTime)info.GetValue("TimeStamp", typeof(DateTime));
            IndexMessage = (int)info.GetValue("IndexMessage", typeof(int));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("From", From);
            info.AddValue("To", To);
            info.AddValue("Data", Data);
            info.AddValue("TimeStamp", TimeStamp);
            info.AddValue("IndexMessage", IndexMessage);
        }

        public byte[] ToByte()
        {
            var stream = new MemoryStream();
            var bformatter = new BinaryFormatter();

            bformatter.Serialize(stream, this);
            return stream.ToArray();
        }
        public static UPD_Message FromByte(byte[] bBuf)
        {
            var um = new UPD_Message();
            var ms = new MemoryStream(bBuf);
            var bf = new BinaryFormatter();

            um = (UPD_Message)bf.Deserialize(ms);
            ms.Close();
            return um;
        }

    }

    public class UDP_Client : INotifyPropertyChanged
    {
        List<byte[]> lstSendBuf = new List<byte[]>();
        private int MainIndexFrame = 0;
        private const int MaxSizeBuf = 50000;
        Thread th_send;
        private DateTime dt_dateRate = DateTime.Now;
        private int BytesDataRateSend = 0;

        #region Property

        private int _BytesInSec;
        public int BytesInSec
        {
            get { return _BytesInSec; }
            set
            {
                _BytesInSec = value;
                SBytesInSec = ((((double)_BytesInSec / 1024) / 1024)).ToString("F3");
                OnPropertyChanged("BytesInSec");
            }
        }

        private bool _joinMulti;
        public bool JoinMulti
        {
            get { return _joinMulti; }
            set
            {
                _joinMulti = value;
                OnPropertyChanged("JoinMulti");
            }
        }


        private string _SBytesInSec;
        public string SBytesInSec
        {
            get { return _SBytesInSec; }
            set
            {
                _SBytesInSec = value;
                OnPropertyChanged("SBytesInSec");
            }
        }

        private int _NumSendPacket;
        public int NumSendPacket
        {
            get { return _NumSendPacket; }
            set
            {
                _NumSendPacket = value;
                OnPropertyChanged("NumSendPacket");
            }
        }


        private string _sSendNumBytes;
        public string SSendNumBytes
        {
            get { return _sSendNumBytes; }
            set
            {
                _sSendNumBytes = value;
                OnPropertyChanged("SSendNumBytes");
            }
        }

        private int _SendNumBytes;
        public int SendNumBytes
        {
            get { return _SendNumBytes; }
            set
            {
                _SendNumBytes = value;
                SSendNumBytes = ((((double)_SendNumBytes / 1024) / 1024)).ToString("F3");
                OnPropertyChanged("SendNumBytes");
            }
        }

        private int _serverPort;
        public int ServerPort
        {
            get { return _serverPort; }
            set
            {
                _serverPort = value;
                OnPropertyChanged("ServerPort");
            }
        }

        private string _serverIp;
        public string ServerIP
        {
            get { return _serverIp; }
            set
            {
                _serverIp = value;
                OnPropertyChanged("ServerIP");
            }
        }
        #endregion

        public void Start()
        {
            Stop();
            if(JoinMulti==false)
                th_send = new Thread(new ThreadStart(SendPacket));
            else th_send = new Thread(new ThreadStart(SendJoinPacket));
        //    th_send.IsBackground = true;
         //   th_send.Priority = ThreadPriority.Highest;
            th_send.Start();
        }

        public UDP_Client()
        {

        }

        public void Send64Buffer(byte[] Buffer)
        {
            if (Buffer == null) return;
            if (Buffer.Length > 64000) return;
            // Creation Socket
            Socket client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            // Conncet with the server
            IPEndPoint ipend = new IPEndPoint(IPAddress.Parse(ServerIP), ServerPort);

            // Send the Text

            client_socket.SendTo(Buffer, ipend);
            // Conncetion Close
            client_socket.Close();

        }

        public void Stop()
        {
            try
            {
                th_send.Abort();

            }
            catch (Exception)
            {
                
            }


            lstSendBuf.Clear();
        }

        void SendJoinPacket()
        {
            UdpClient udpclient = new UdpClient(ServerPort);
            IPAddress multicastaddress = IPAddress.Parse(ServerIP);
            udpclient.JoinMulticastGroup(multicastaddress);
            IPEndPoint remoteep = new IPEndPoint(multicastaddress, ServerPort);

            while (true)
            {
                if (lstSendBuf.Count == 0) Thread.Sleep(1);
                else
                {
                    byte[] Buffer = null;
                    lock (lstSendBuf)
                    {
                        Buffer = lstSendBuf[0];
                        lstSendBuf.RemoveAt(0);
                    }

                    int FullSize = Buffer.Length;
                    byte NumPacket = (byte)(FullSize / MaxSizeBuf);
                    if (FullSize % MaxSizeBuf != 0)
                    {
                        NumPacket++;
                    }
                    byte CurPacket = 0;
                    int SendSize = 0;
                    MainIndexFrame++;

                    while (FullSize != 0)
                    {
                        if (FullSize > MaxSizeBuf)
                            SendSize = MaxSizeBuf;
                        else SendSize = FullSize;

                        var up = new UDP_Packet
                        {
                            Data = new byte[SendSize],
                            Index = MainIndexFrame,
                            NumPacket = NumPacket,
                            CurPacket = (CurPacket++)
                        };

                        Array.Copy(Buffer, Buffer.Length - FullSize, up.Data, 0, SendSize);

                        byte[] bSend = up.ToByte();
                        SendNumBytes += bSend.Length;
                        BytesDataRateSend += bSend.Length;
                        NumSendPacket++;
                        //      client_socket.BeginSendTo(bSend, 0, bSend.Length, SocketFlags.None,ipend, CallBackSend, client_socket);
                      //  client_socket.SendTo(bSend, ipend);
                        udpclient.Send(bSend, bSend.Length, remoteep);
                        FullSize -= SendSize;
                    }

                    if ((DateTime.Now - dt_dateRate).TotalMilliseconds >= 1000)
                    {
                        dt_dateRate = DateTime.Now;
                        BytesInSec = BytesDataRateSend;
                        BytesDataRateSend = 0;
                    }

                    if (lstSendBuf.Count > 1000)
                    {

                    }
                }
            }

        }


        void SendPacket()
        {
            while (true)
            {
                if(lstSendBuf.Count==0) Thread.Sleep(1);
                else
                {
                    byte[] bBuf = lstSendBuf[0];
                    SendBuf(bBuf);
                    lstSendBuf.RemoveAt(0);

                    if(lstSendBuf.Count>1000)
                    {
                        
                    }
                }
            }
        }

        public void SendBigBuffer(byte[] Buffer)
        {
            if (Buffer == null) return;

            lock(lstSendBuf)
            {
                lstSendBuf.Add(Buffer);
            }
        }
        //*
        private void SendBuf(byte[] Buffer)
        {
            if (Buffer == null) return;
            // Creation Socket
            Socket client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            // Conncet with the server
            IPEndPoint ipend = new IPEndPoint(IPAddress.Parse(ServerIP), ServerPort);

            // Send the Text
            int FullSize = Buffer.Length;
            byte NumPacket = (byte)(FullSize/MaxSizeBuf);
            if(FullSize%MaxSizeBuf!=0)
            {
                NumPacket++;
            }
            byte CurPacket = 0;
            int SendSize = 0;
            MainIndexFrame++;

            while (FullSize!=0)
            {
                if (FullSize > MaxSizeBuf)
                    SendSize = MaxSizeBuf;
                else SendSize = FullSize;

                var up = new UDP_Packet
                                    {
                                        Data = new byte[SendSize],
                                        Index = MainIndexFrame,
                                        NumPacket = NumPacket,
                                        CurPacket = (CurPacket++)
                                    };

                Array.Copy(Buffer, Buffer.Length - FullSize, up.Data, 0, SendSize);

                byte[] bSend = up.ToByte();
                SendNumBytes += bSend.Length;
                BytesDataRateSend += bSend.Length;
                NumSendPacket++;
          //      client_socket.BeginSendTo(bSend, 0, bSend.Length, SocketFlags.None,ipend, CallBackSend, client_socket);
                client_socket.SendTo(bSend, ipend);
                FullSize -= SendSize;
            }
            
            // Conncetion Close
            client_socket.Close();

            if((DateTime.Now - dt_dateRate).TotalMilliseconds>=1000)
            {
                dt_dateRate = DateTime.Now;
                BytesInSec = BytesDataRateSend;
                BytesDataRateSend = 0;
            }
        }//*/

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }

    public class UDP_Server : INotifyPropertyChanged
    {

        Thread th;
        Thread th_AnalisePacket;
        Socket sock;
        private DateTime dt_dateRate = DateTime.Now;
        private int BytesDataRate = 0;

        #region Property
        
        private int _serverPort;
        public int ServerPort
        {
            get { return _serverPort; }
            set
            {
                _serverPort = value;
                OnPropertyChanged("ServerPort");
            }
        }
        private bool _joinMulti;
        public bool JoinMulti
        {
            get { return _joinMulti; }
            set
            {
                _joinMulti = value;
                OnPropertyChanged("JoinMulti");
            }
        }
        private string _serverIp;
        public string ServerIP
        {
            get { return _serverIp; }
            set
            {
                _serverIp = value;
                OnPropertyChanged("ServerIP");
            }
        }

        private int _BytesInSec;
        public int BytesInSec
        {
            get { return _BytesInSec; }
            set
            {
                _BytesInSec = value;
                SBytesInSec = ((((double)_BytesInSec / 1024) / 1024)).ToString("F3");
                OnPropertyChanged("BytesInSec");
            }
        }

        private string _SBytesInSec;
        public string SBytesInSec
        {
            get { return _SBytesInSec; }
            set
            {
                _SBytesInSec = value;
                OnPropertyChanged("SBytesInSec");
            }
        }

        private int _NumRecPacket;
        public int NumRecPacket
        {
            get { return _NumRecPacket; }
            set
            {
                _NumRecPacket = value;
                OnPropertyChanged("NumRecPacket");
            }
        }


        private string _sRecNumBytes;
        public string SRecNumBytes
        {
            get { return _sRecNumBytes; }
            set
            {
                _sRecNumBytes = value;
                OnPropertyChanged("SRecNumBytes");
            }
        }

        private int _RecNumBytes;
        public int RecNumBytes
        {
            get { return _RecNumBytes; }
            set
            {
                _RecNumBytes = value;
                SRecNumBytes = ((((double)_RecNumBytes / 1024) / 1024)).ToString("F3");
                OnPropertyChanged("RecNumBytes");
            }
        }

        #endregion

        public void Start()
        {
            if(JoinMulti==false)
                th = new Thread(new ThreadStart(Server));
            else th = new Thread(new ThreadStart(JoinServer));
            
  
           // th.IsBackground = true;
            th.Priority = ThreadPriority.Highest;
            th.Start();

            th_AnalisePacket = new Thread(new ThreadStart(AnalisePacket));
          //  th_AnalisePacket.IsBackground = true;
            th_AnalisePacket.Priority = ThreadPriority.Highest;
            th_AnalisePacket.Start();
        }

        public UDP_Server()
        {

        }

        public void Stop()
        {

            try
            {
                sock.Close();
            }
            catch (Exception)
            {

            }

            try
            {
                th.Abort();
            }
            catch (Exception)
            {
                
            }

            try
            {
                th_AnalisePacket.Abort();
            }
            catch (Exception)
            {
                
            }

            try
            {
                if (udpclient!=null)
                    udpclient.Close();
            }
            catch (Exception)
            {
                
            }
            lstP.Clear();
            LstRecPacket.Clear();
        }
        Queue<UDP_Packet> LstRecPacket = new Queue<UDP_Packet>();
        void AnalisePacket()
        {
            
            while (true)
            {
                try
                {
                    if (LstRecPacket.Count != 0)
                    lock (LstRecPacket)
                    {
                            lstP.Add(LstRecPacket.Dequeue());
                    }
                    if (lstP.Count > 1)
                        {
                                byte[] b = CheckPacket();
                                if (b != null)
                                {
                                    CallBackPacket(b);
                                }
                        }
                    Thread.Sleep(1);
                }
                catch (Exception ex)
                {

                }
            }
        }

        public delegate void ReceiveBuffer(byte[] bByf);

 //       private List<UDP_Packet> LstRecPacket = new List<UDP_Packet>();
        public event ReceiveBuffer evRecBuf;
        private int NumErrPacket;
        List<UDP_Packet> lstP = new List<UDP_Packet>();
        private int SaveIndex = 0;

        byte[]  CheckPacket()
        {
            DateTime dt = DateTime.Now;
            byte[] bufFullSize = null;
            lstP.RemoveAll(p => (dt - p.timeRec).TotalMilliseconds > 500);

            if (lstP.Count != 0)
                SaveIndex = lstP[lstP.Count - 1].Index;


            foreach (var up in lstP)// (int i = 0; i < LstRecPacket.Count;i++)
            {
                int curIndex = up.Index;
                int numP = up.NumPacket;
                int countFind = lstP.Count(p => p.Index == curIndex);

                if (countFind == numP)
                {
                    var lst = lstP.Where(p => p.Index == curIndex).ToList();

                    int fullsize = lst.Aggregate(0, (current, uu) => current + uu.Size);

                     bufFullSize = new byte[fullsize];

                    var curPos = 0;
                    foreach (var uu in lst)
                    {
                        NumRecPacket++;

                        Array.Copy(uu.Data, 0, bufFullSize, curPos, uu.Size);
                        curPos += uu.Size;
                    }
                    SaveIndex = curIndex;
                    lstP.RemoveAll(p => p.Index == SaveIndex);
                    break;
                }
            }
        //    lstP.RemoveAll(p => p.Index < SaveIndex);

            return bufFullSize;
        }

        void CallBackPacket(byte[] data)
        {
            DateTime dt = DateTime.Now;
            BytesDataRate += data.Length;

            RecNumBytes += data.Length;

            evRecBuf(data);

            if ((DateTime.Now - dt_dateRate).TotalMilliseconds >= 1000)
            {
                dt_dateRate = DateTime.Now;
                BytesInSec = BytesDataRate;
                BytesDataRate = 0;
            }
        }

        private UdpClient udpclient;
        void JoinServer()
        {
             udpclient = new UdpClient(ServerPort);
            IPAddress multicastaddress = IPAddress.Parse(ServerIP);
            udpclient.JoinMulticastGroup(multicastaddress);
            IPEndPoint remote = null;            
            // Lestening
            var buffer = new byte[64000];

            while (true)
            {
                try
                {
                    buffer = udpclient.Receive(ref remote);
                    if (evRecBuf != null)
                    {
                        var up = UDP_Packet.FromByte(buffer, buffer.Length);

                        lock (LstRecPacket)
                        {
                            LstRecPacket.Enqueue(up);
                        }

                    }
                    Thread.Sleep(1);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }

        }

        void Server()
        {
            // Creation Socket
            var ap = IPAddress.Loopback;
            sock = new Socket(ap.AddressFamily, SocketType.Dgram, ProtocolType.Udp);

            // Socket Binding 
            var ipend = new IPEndPoint(IPAddress.Any, ServerPort);
            sock.Bind(ipend);

            var mo = new MulticastOption(IPAddress.Parse(ServerIP));
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, mo);

            // Lestening
            var buffer = new byte[64000];

            while (true)
            {
                try
                {
                    var ip = (EndPoint)ipend;
                    long R = sock.ReceiveFrom(buffer, ref ip);

                    if(evRecBuf!=null)
                    {
                        var up = UDP_Packet.FromByte(buffer, R);

                        lock (LstRecPacket)
                        {
                            LstRecPacket.Enqueue(up);
                        }

                    }
                    Thread.Sleep(1);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }

        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}
